#!/usr/bin/env python3
# -*- coding: utf-8 -*-


from sarena import *
from utils import *
import minimax
import random
from collections import OrderedDict

class SuperPlayer(Player):

    def __init__(self):
        self.nb_nodes = {}
        self.actions_by_step = {}
    
    def priority_util(self, node):
        "Evaluation of a given state"
        action, state = node
        return self.evaluate(state)

    def successors(self, state):
        board, player = state
        actions = board.get_actions()
        
        current_score = self.evaluate(state)    # score of current state
        kept_state = PriorityQueue(self.priority_util, player < 0 and min or max)

        for action in actions:
            visited_node_state = (board.clone().play_action(action), -player)
            visited_node = (action, visited_node_state)

            visited_node_score = self.evaluate(visited_node_state)
            
            # if current state has better score than visited one, we stock it in priority queue
            # when all state has been visited, we return the stocked ones (this should make alphabeta pruning better)
            if player * (visited_node_score - current_score) < 0: 
                kept_state.append(visited_node)
                self.successors(state)
            else:
                yield action,(board.clone().play_action(action), -player)

        while kept_state:
            yield kept_state.pop()

    def cutoff(self, state, depth, time_left, elapsed_time, step):
        board, player = state

        #--- Count nb of nodes traversed by depth  3._ question -----------
        #if str(depth) not in self.nb_nodes.keys() :
        #    self.nb_nodes[str(depth)] = 1
        #else:
        #    self.nb_nodes[str(depth)] += 1           
        
        if time_left is not None:
            nb_steps_left = 32 - step
            if nb_steps_left<1: return True
            avrg_time_by_step = time_left/nb_steps_left
            
            # Need to cut if we are in the last seconds
            if time_left<40 : 
                return True
            
            # Need to cut if we are in the first steps
            elif step<5 :
                if avrg_time_by_step<elapsed_time:
                    return True
                elif depth>3 : # first steps are cut often
                    return True
            elif avrg_time_by_step<elapsed_time:
                return True 

        return board.is_finished() 
            
    def evaluate(self, state):
        """Return a score (heuristics) for this state.
    
        The score is the difference between the number of weighted towers of each
        player. In case of ties, it is the difference between the maximal
        height towers of each player. If self.cutoff() returns True,
        this score represents the winner (<0: red, >0: yellow, 0: draw).
    
        """
        board, player = state
        score = 0
        for i in range(board.rows):
            for j in range(board.columns):
                tower = board.get_tower(board.m[i][j])
                if tower:
                    val_circle = 1
                    if len(tower)==4:
                        val_circle = 4
                            
                    if board.m[i][j][0] == 4 : # arrows
                        if tower[-1][1] == 1: # top is yello
                            score += val_circle*len(tower)
                        elif tower[-1][1] == -1: #top is red
                            score -= val_circle*len(tower)   
                    else: # normal circle
                        if tower[0][0]==-1: # bottom is red
                            if tower[-1][1] == -1: # top is red
                                score -= 3*val_circle*(5-len(tower))
                            elif tower[-1][1] == 1: # top is yellow
                                score -= 2*val_circle*(5-len(tower))
                            else: 
                                score -= 1*val_circle*(5-len(tower))
                                
                        elif tower[0][0] == 1: # bottom yellow
                            if tower[-1][1] == 1: # top is yello
                                score += 3*val_circle*(5-len(tower))
                            elif tower[-1][1] == -1: #top is red
                                score += 2*val_circle*(5-len(tower))
                            else:
                                score += 1*val_circle*(5-len(tower))
                            
        if score == 0:
            
            for i in range(board.rows):
                for j in range(board.columns):
                    tower = board.get_tower(board.m[i][j])
                    if tower:
                        if tower[-1][1] == -1:
                            for half_tok in filter(lambda token:
                                                       (token[0] == -1 or
                                                        token[1] == -1),
                                                   tower):
                                score -= 1
                        elif tower[-1][1] == 1:
                            for half_tok in filter(lambda token:
                                                       (token[0] == 1 or
                                                        token[1] == 1),
                                                   tower):
                                score += 1
        return score
        
    def play(self, percepts, step, time_left):
        if step % 2 == 0:
            player = -1
        else:
            player = 1
        board = Board(percepts)
        state = (board, player)
        
        """For the question 3.11        
        if str(step) not in self.actions_by_step.keys() :
            self.actions_by_step[str(step)] = len(list(board.get_actions()))
        else:
            self.actions_by_step[str(step)] += len(list(board.get_actions()))
        
        print("Average per step :")
        d_sorted = OrderedDict(sorted(self.actions_by_step.items(), key=lambda x: x[1]))

        for k, v in d_sorted.items():
            print("%s: %s" % (k, v))
        #for key, val in self.actions_by_step.items():
        #    print(key, "=>", str(val))
        """  
        return minimax.search(state, self, time_left, step)
            
        
    def get_nb_nodes(self):
        return self.nb_nodes.copy()

    
if __name__ == "__main__":
    player_main(SuperPlayer())
